﻿namespace DatabaseDoc.Library
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Text.RegularExpressions;

    using global::DatabaseDoc.Library.Factories;

    /// <summary>
    /// Database documentation
    /// </summary>
    public class DatabaseDoc
    {        
        /// <summary>
        /// Initializes a new instance of the <see cref="DatabaseDoc"/> class.
        /// </summary>
        /// <param name="connectorKey">The connector key.</param>
        public DatabaseDoc(string connectorKey)
        {
            var databaseConnectorFactory = new DatabaseConnectorFactory();
            this.DatabaseConnector = databaseConnectorFactory.CreateDatabaseConnector(connectorKey);

            if (this.DatabaseConnector == null)
            {
                throw new ConnectorNotFoundException(string.Format("Connector {0} not found", connectorKey));
            }
        }

        /// <summary>
        /// Gets or sets the servers.
        /// </summary>
        /// <value>The servers.</value>
        public IList<Server> Servers { get; set; }

        /// <summary>
        /// Gets or sets the database connector.
        /// </summary>
        /// <value>The database connector.</value>
        private IDatabaseConnector DatabaseConnector { get; set; }

        /// <summary>
        /// Generates the specified server names.
        /// </summary>
        /// <param name="serverNames">The server names.</param>
        /// <param name="databaseName">Name of the database.</param>
        /// <param name="tableName">Name of the table.</param>
        /// <returns>The database doc structure</returns>
        public DatabaseDoc Populate(string[] serverNames, string databaseName = null, string tableName = null)
        {
            var databaseDoc = this.DatabaseConnector.Populate(serverNames, databaseName, tableName);
            return databaseDoc;
        }

        public void Output(string fileName, string outputterKey)
        {
            var outputterFactory = new OutputterFactory();
            var outputter = outputterFactory.CreateOutputter(outputterKey);

            if (outputter == null)
            {
                throw new OutputterNotFoundException(string.Format("Outputter {0} not found", outputterKey));
            }

            outputter.Output(this, fileName);
        }

        public void GenerateScripts(string serverName, string databaseName, string tableName, string scriptPrefix)
        {
            this.DatabaseConnector.GenerateScripts(serverName, databaseName, tableName, scriptPrefix);
        }

        // TODO: runScriptVersion should be a version, not a string
        // TODO: should be collection of Script type
        public IEnumerable<ScriptVersion> GetRunScriptVersions(string runScriptValue)
        {
            var path = Environment.CurrentDirectory;
            var directoryInfo = new DirectoryInfo(path);

            // Get a list of all ordered *.sql files that are convertable to versions
            var versionSqlFiles = directoryInfo.GetFiles("*.sql").Select(
                fi =>
                    {
                        var firstSpaceIndex = fi.Name.IndexOf(' ');
                        var fileName = firstSpaceIndex == -1 ? fi.Name : fi.Name.Substring(0, firstSpaceIndex);

                        Version version;
                        return Version.TryParse(fileName, out version)
                                   ? new ScriptVersion { FileInfo = fi, Version = version }
                                   : null;
                    });

            versionSqlFiles =
                versionSqlFiles.Where(sv => sv != null).OrderBy(sv => sv.Version.Major).ThenBy(sv => sv.Version.Minor).
                    ThenBy(sv => sv.Version.Build).ThenBy(sv => sv.Version.Revision);

            if (string.IsNullOrWhiteSpace(runScriptValue))
            {
                return versionSqlFiles;
            }

            // If a run script value has been specified, then it should be in the format:
            // 1.6.0.1-1.8.0.1
            // or *-1.8.0.1 
            // or 1.5.0.1-*
            var hyphenCount = runScriptValue.Count(c => c == '-');
            if (hyphenCount != 1)
            {
                throw new Exception(string.Format("Run version scripts value of '{0}' is invalid", runScriptValue));
            }

            var hypenIndex = runScriptValue.IndexOf("-");
            var minVersionFormat = runScriptValue.Substring(0, hypenIndex).Trim();
            var maxVersionFormat = runScriptValue.Substring(hypenIndex + 1);

            Version minVersion = null;
            if (minVersionFormat != "*")
            {
                if (!Version.TryParse(minVersionFormat, out minVersion))
                {
                    throw new Exception(string.Format("Minimum version format is invalid '{0}'", minVersionFormat));
                }
            }

            Version maxVersion = null;
            if (maxVersionFormat != "*")
            {
                if (!Version.TryParse(maxVersionFormat, out maxVersion))
                {
                    throw new Exception(string.Format("Maximum version format if invalid '{0}'", maxVersionFormat));
                }
            }

            if (minVersion != null)
            {
                versionSqlFiles = versionSqlFiles.Where(sv => sv.Version >= minVersion);
            }

            if (maxVersion != null)
            {
                versionSqlFiles = versionSqlFiles.Where(sv => sv.Version <= maxVersion);
            }

            return versionSqlFiles;
        }

        public void RunScripts(string serverName, string databaseName, IEnumerable<FileInfo> runScripts)
        {                        
            var fileContents = new List<string>(runScripts.Count());

            foreach (var sqlFile in runScripts)
            {
                var fileContent = File.ReadAllText(sqlFile.FullName);
                fileContents.Add(fileContent);                
            }

            if (fileContents.Count() == 0)
            {
                return;
            }

            this.DatabaseConnector.RunScripts(serverName, databaseName, fileContents);
        }

        public bool DatabaseExists(string serverName, string databaseName)
        {
            return this.DatabaseConnector.DatabaseExists(serverName, databaseName);
        }

        public void CreateDatabase(string serverName, string databaseName)
        {
            this.DatabaseConnector.CreateDatabase(serverName, databaseName);
        }

        public void DropDatabase(string serverName, string databaseName)
        {
            this.DatabaseConnector.DropDatabase(serverName, databaseName);
        }

        public void RunScripts(string serverName, string databaseName, IEnumerable<string> scripts)
        {
            this.DatabaseConnector.RunScripts(serverName, databaseName, scripts);
        }
    }

    public class ScriptVersion
    {
        public FileInfo FileInfo { get; set; }

        public Version Version { get; set; }

        public string VersionAsString
        {
            get
            {
                return this.Version != null ? this.Version.ToString() : null;
            }
        }
    }
}